//===--- ParameterPassing.swift -------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// RUN: %empty-directory(%t)
// RUN: %gyb %s -o %t/ParameterPassing.swift
// RUN: %line-directive %t/ParameterPassing.swift -- %target-build-swift %t/ParameterPassing.swift -o %t/a.out_Release -O
// RUN: %target-run %t/a.out_Release
// REQUIRES: executable_test
// REQUIRES: long_test

import StdlibUnittest

let tests = TestSuite("ParameterPassing")

struct MyError : Error {
   let val = 127
}

%{
   errorMethodTypes = [
     ('Throwing', 'throws', True, 'true'),
     ('Throws',  'throws', True, 'false'),
     ('', '', False, 'false'),
   ]
}%

// Float values.
% for TestType in 'Float', 'Double':
% for Num in range(0, 10):
  @inline(__always)
  func value${TestType}${Num}() -> ${TestType} {
    return 1.${Num+1}
  }
% end
% end

// Integer values.
% for TestType in 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Int8', 'Int16', 'Int32', 'Int64':

  enum Enum${TestType} : Equatable {
    case Empty
    case Value(${TestType})
  }
  func == (lhs: Enum${TestType}, rhs: Enum${TestType}) -> Bool {
    switch lhs {
      case .Empty:
        switch rhs {
          case .Empty:
            return true
          case .Value(_):
            return false
        }
      case .Value(let lhsVal):
        switch lhs {
           case .Empty:
             return false
           case .Value(let rhsVal):
            return lhsVal == rhsVal
        }
    }
  }

  struct Struct${TestType} : Equatable {
    let f0 : ${TestType}
    let f1: Float
    let f2: Double
    init(f0: ${TestType}, f1: Float, f2: Double) {
      self.f0 = f0
      self.f1 = f1
      self.f2 = f2
    }
  }
  func ==(lhs: Struct${TestType}, rhs: Struct${TestType}) -> Bool {
    return lhs.f0 == rhs.f0 && lhs.f1 == rhs.f1 && lhs.f2 == rhs.f2
  }

% for Num in range(0, 10):
  @inline(__always)
  func value${TestType}${Num}() -> ${TestType} {
    return ${Num+1}
  }
  @inline(__always)
  func valueEnum${TestType}${Num}() -> Enum${TestType} {
    return Enum${TestType}.Value(${Num+1})
  }
  func valueStruct${TestType}${Num}() -> Struct${TestType} {
    return Struct${TestType}(f0: ${Num+1}, f1: 1.${Num}, f2: 2.${Num})
  }
% end
% end

// Float80 values.
#if arch(i386) || arch(x86_64)
% for Num in range(0, 10):
  @inline(__always)
  func valueFloat80${Num}() -> Float80 {
    return 1.${Num+1}
  }
% end
#endif

% for (FuncName, Throw, MayThrow, DoesThrow) in errorMethodTypes:

@inline(never)
func clobber${FuncName}(
  _ d0: Double, _ d1: Double, _ d2: Double, _ d3: Double, _ d4: Double, _ d5: Double,
  _ d6: Double, _ d7: Double, _ d8: Double, _ d9: Double, _ i0: Int, _ i1: Int,
  _ i2: Int, _ i3: Int, _ i4: Int, _ i5: Int, _ i6: Int, _ i7: Int, _ i8: Int,
  _ i9: Int
) ${Throw} -> (Double, Int) {
  let sumD = d0 + d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8 + d9
  let sumI = i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9
% if MayThrow:
  var shouldThrow = ${DoesThrow}
  withUnsafeMutablePointer(to: &shouldThrow, { _blackHole($0) })
  if shouldThrow {
    throw MyError()
  }
% end
  return (sumD, sumI)
}

%end

%{ testTypes = [
     'Float', 'Float80', 'Double', 'UInt8', 'UInt16', 'UInt32', 'UInt64',
     'Int8', 'Int16', 'Int32', 'Int64',
     'EnumUInt8', 'EnumUInt16', 'EnumUInt32', 'EnumUInt64', 'EnumInt8',
     'EnumInt16', 'EnumInt32', 'EnumInt64',
     'StructUInt8', 'StructUInt16', 'StructUInt32', 'StructUInt64',
     'StructInt8', 'StructInt16', 'StructInt32', 'StructInt64',
   ]
}%
% for TestType in testTypes:

% if TestType == 'Float80':
#if arch(i386) || arch(x86_64)
% end

% for (FuncName, Throw, MayThrow, DoesThrow) in errorMethodTypes:

@inline(never)
func verifyParameters${TestType}${FuncName}(
  _ p0: ${TestType}, _ p1: ${TestType}, _ p2: ${TestType}, _ p3: ${TestType}, _ p4: ${TestType}, _ p5: ${TestType},
  _ p6: ${TestType}, _ p7: ${TestType}, _ p8: ${TestType}, _ p9 : ${TestType}
) ${Throw} {
% for Num in range(0, 10):
   precondition(p${Num} == value${TestType}${Num}())
% end
% if MayThrow:
   let res = try clobber${FuncName}(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
% else:
   let res = clobber${FuncName}(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
% end
                          2, 3, 5, 7, 11, 13, 17, 19, 23, 29)
   precondition(res.0 == 5.5)
   precondition(res.1 == 129)
% for Num in range(0, 10):
   precondition(p${Num} == value${TestType}${Num}())
% end
}

@inline(never)
func verifyReturn${TestType}${FuncName}() ${Throw} -> (
% for Num in range(0, 9):
  ${TestType},
% end
  ${TestType}
  )
{
% if MayThrow:
  var shouldThrow = ${DoesThrow}
  withUnsafeMutablePointer(to: &shouldThrow, { _blackHole($0) })
  if shouldThrow {
    throw MyError()
  }
% end

  return (
% for Num in range(0, 9):
  value${TestType}${Num}(),
% end
  value${TestType}9()
  )
}

@inline(never)
func verifyReturnAndParameters${TestType}${FuncName}(
  _ p0: ${TestType}, _ p1: ${TestType}, _ p2: ${TestType}, _ p3: ${TestType}, _ p4: ${TestType}, _ p5: ${TestType},
  _ p6: ${TestType}, _ p7: ${TestType}, _ p8: ${TestType}, _ p9 : ${TestType}
) ${Throw} -> (
% for Num in range(0, 9):
  ${TestType},
% end
  ${TestType}
  )
{
% for Num in range(0, 10):
   precondition(p${Num} == value${TestType}${Num}())
% end
% if MayThrow:
   let res = try clobber${FuncName}(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
% else:
   let res = clobber${FuncName}(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
% end
                          2, 3, 5, 7, 11, 13, 17, 19, 23, 29)
   precondition(res.0 == 5.5)
   precondition(res.1 == 129)
% for Num in range(0, 10):
   precondition(p${Num} == value${TestType}${Num}())
% end
  return (
% for Num in range(0, 9):
  value${TestType}${Num}(),
% end
  value${TestType}9()
  )
}
% end

protocol Proto${TestType} {
% for (FuncName, Throw, _, _) in errorMethodTypes:
  func verifyParameters${FuncName}(
    _ p0: ${TestType}, _ p1: ${TestType}, _ p2: ${TestType}, _ p3: ${TestType}, _ p4: ${TestType}, _ p5: ${TestType},
    _ p6: ${TestType}, _ p7: ${TestType}, _ p8: ${TestType}, _ p9 : ${TestType}
  ) ${Throw}

  func verifyReturn${FuncName}() ${Throw} -> (
% for Num in range(0, 9):
  ${TestType},
% end
  ${TestType}
  )

  @inline(never)
  func verifyReturnAndParameters${FuncName}(
    _ p0: ${TestType}, _ p1: ${TestType}, _ p2: ${TestType}, _ p3: ${TestType}, _ p4: ${TestType}, _ p5: ${TestType},
    _ p6: ${TestType}, _ p7: ${TestType}, _ p8: ${TestType}, _ p9 : ${TestType}
  ) ${Throw} -> (
% for Num in range(0, 9):
    ${TestType},
% end
    ${TestType}
  )
%end
}

public class A${TestType} : Proto${TestType} {
  var dontStripSelfArg: Int = 7

  init() {}

% for (FuncName, Throw, MayThrow, _) in errorMethodTypes:
  @inline(never)
  func verifyParameters${FuncName}(
    _ p0: ${TestType}, _ p1: ${TestType}, _ p2: ${TestType}, _ p3: ${TestType}, _ p4: ${TestType}, _ p5: ${TestType},
    _ p6: ${TestType}, _ p7: ${TestType}, _ p8: ${TestType}, _ p9 : ${TestType}
  ) ${Throw} {
% for Num in range(0, 10):
   precondition(p${Num} == value${TestType}${Num}())
% end
% if MayThrow:
     let res = try clobber${FuncName}(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
% else:
     let res = clobber${FuncName}(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
% end
               2, 3, 5, 7, 11, 13, 17, 19, 23, 29)
     precondition(dontStripSelfArg == 7)
     precondition(res.0 == 5.5)
     precondition(res.1 == 129)
% for Num in range(0, 10):
   precondition(p${Num} == value${TestType}${Num}())
% end
  }

  func verifyReturn${FuncName}() ${Throw} -> (
% for Num in range(0, 9):
    ${TestType},
% end
    ${TestType}
    )
  {
% if MayThrow:
    var shouldThrow = ${DoesThrow}
    withUnsafeMutablePointer(to: &shouldThrow, { _blackHole($0) })
    if shouldThrow {
      throw MyError()
    }
% end

    return (
% for Num in range(0, 9):
    value${TestType}${Num}(),
% end
    value${TestType}9()
    )
  }

  @inline(never)
  func verifyReturnAndParameters${FuncName}(
    _ p0: ${TestType}, _ p1: ${TestType}, _ p2: ${TestType}, _ p3: ${TestType}, _ p4: ${TestType}, _ p5: ${TestType},
    _ p6: ${TestType}, _ p7: ${TestType}, _ p8: ${TestType}, _ p9 : ${TestType}
  ) ${Throw} -> (
% for Num in range(0, 9):
    ${TestType},
% end
    ${TestType}
    )
  {
% for Num in range(0, 10):
     precondition(p${Num} == value${TestType}${Num}())
% end
% if MayThrow:
     let res = try clobber${FuncName}(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
% else:
     let res = clobber${FuncName}(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
% end
                            2, 3, 5, 7, 11, 13, 17, 19, 23, 29)
     precondition(res.0 == 5.5)
     precondition(res.1 == 129)
% for Num in range(0, 10):
     precondition(p${Num} == value${TestType}${Num}())
% end
    return (
% for Num in range(0, 9):
    value${TestType}${Num}(),
 % end
    value${TestType}9()
    )
  }

% end
}

% for (FuncName, throw, MayThrow, DoesThrow) in errorMethodTypes:
tests.test("${TestType}/Fun${FuncName}") {
  // Test a regular function call.
% if MayThrow:
  do {
    try verifyParameters${TestType}${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
    )
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
     preconditionFailure("Should not get there")
  }
% else:
  verifyParameters${TestType}${FuncName}(
%for Num in range(0, 9):
    value${TestType}${Num}(),
%end
    value${TestType}9()
  )
% end
}

tests.test("${TestType}/RetFun${FuncName}") {
  // Test a regular function call.
% if MayThrow:
  do {
    let res = try verifyReturn${TestType}${FuncName}()
% for Num in range(0, 10):
    expectEqual(value${TestType}${Num}(), res.${Num})
% end
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
    preconditionFailure("Should not get there")
  }
% else:
  let res = verifyReturn${TestType}${FuncName}()
%for Num in range(0, 10):
  expectEqual(value${TestType}${Num}(), res.${Num})
% end
% end
}

tests.test("${TestType}/RetAndParamFun${FuncName}") {
  // Test a regular function call.
% if MayThrow:
  do {
    let res = try verifyReturnAndParameters${TestType}${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
    )
% for Num in range(0, 10):
    expectEqual(value${TestType}${Num}(), res.${Num})
% end
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
     preconditionFailure("Should not get there")
  }
% else:
  let res = verifyReturnAndParameters${TestType}${FuncName}(
%for Num in range(0, 9):
    value${TestType}${Num}(),
%end
    value${TestType}9()
  )
% for Num in range(0, 10):
  expectEqual(value${TestType}${Num}(), res.${Num})
% end
% end
}

tests.test("${TestType}/Method${FuncName}") {
  var klazz = A${TestType}()
  // Defeat type analysis such that the call below remains a method call.
  withUnsafeMutablePointer(to: &klazz, { _blackHole($0) })

  // Test a method call.
% if MayThrow:
  do {
    try klazz.verifyParameters${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
    )
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
     preconditionFailure("Should not get there")
  }
% else:
  klazz.verifyParameters${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
  )
% end
}

tests.test("${TestType}/RetMethod${FuncName}") {
  var klazz = A${TestType}()
  // Defeat type analysis such that the call below remains a method call.
  withUnsafeMutablePointer(to: &klazz, { _blackHole($0) })

  // Test a method call.
% if MayThrow:
  do {
    let res = try klazz.verifyReturn${FuncName}()
% for Num in range(0, 10):
    expectEqual(value${TestType}${Num}(), res.${Num})
% end
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
    preconditionFailure("Should not get there")
  }
% else:
  let res = klazz.verifyReturn${FuncName}()
%for Num in range(0, 10):
  expectEqual(value${TestType}${Num}(), res.${Num})
% end
% end
}

tests.test("${TestType}/RetAndParamMethod${FuncName}") {
  var klazz = A${TestType}()
  // Defeat type analysis such that the call below remains a method call.
  withUnsafeMutablePointer(to: &klazz, { _blackHole($0) })

  // Test a method call.
% if MayThrow:
  do {
    let res = try klazz.verifyReturnAndParameters${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
    )
%for Num in range(0, 10):
    expectEqual(value${TestType}${Num}(), res.${Num})
% end
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
     preconditionFailure("Should not get there")
  }
% else:
  let res = klazz.verifyReturnAndParameters${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
  )
%for Num in range(0, 10):
  expectEqual(value${TestType}${Num}(), res.${Num})
% end
% end
}

tests.test("${TestType}/WitnessMethod${FuncName}") {
  var p : Proto${TestType} = A${TestType}()
  withUnsafeMutablePointer(to: &p, { _blackHole($0) })

  // Test a witness method call.
% if MayThrow:
  do {
    try p.verifyParameters${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
    )
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
     preconditionFailure("Should not get there")
  }
% else:
  p.verifyParameters${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
  )
% end
}

tests.test("${TestType}/RetWitnessMethod${FuncName}") {
  var p : Proto${TestType} = A${TestType}()
  withUnsafeMutablePointer(to: &p, { _blackHole($0) })

  // Test a witness method call.
% if MayThrow:
  do {
    let res = try p.verifyReturn${FuncName}()
% for Num in range(0, 10):
    expectEqual(value${TestType}${Num}(), res.${Num})
% end
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
    preconditionFailure("Should not get there")
  }
% else:
  let res = p.verifyReturn${FuncName}()
%for Num in range(0, 10):
  expectEqual(value${TestType}${Num}(), res.${Num})
% end
% end
}

tests.test("${TestType}/RetAndParamWitnessMethod${FuncName}") {
  var p : Proto${TestType} = A${TestType}()
  withUnsafeMutablePointer(to: &p, { _blackHole($0) })

  // Test a method call.
% if MayThrow:
  do {
    let res = try p.verifyReturnAndParameters${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
    )
%for Num in range(0, 10):
    expectEqual(value${TestType}${Num}(), res.${Num})
% end
% if DoesThrow == 'true':
  } catch let e as MyError {
     expectEqual(127, e.val)
% end
  } catch {
     preconditionFailure("Should not get there")
  }
% else:
  let res = p.verifyReturnAndParameters${FuncName}(
%for Num in range(0, 9):
      value${TestType}${Num}(),
%end
      value${TestType}9()
  )
%for Num in range(0, 10):
  expectEqual(value${TestType}${Num}(), res.${Num})
% end
% end
}

% end

% if TestType == 'Float80':
#endif
% end

% end

runAllTests()
